package com.hyw.cm.server.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

import com.hyw.cm.common.dto.CEntry;
import com.hyw.cm.common.equipment.constant.EquipmentStatus;
import com.hyw.cm.common.equipment.dto.EquipmentDTO;
import com.hyw.cm.common.protocal.spi.IMessage;
import com.hyw.cm.protocal.service.IBusinessProtocalService;
import com.hyw.cm.reg.service.IEquipmentService;
import com.hyw.cm.server.storage.dao.IConnectionRepository;
import com.hyw.cm.session.pubsub.IPubSub;
import com.hyw.cm.session.pubsub.RedisSessionPubSub;

@Configuration(proxyBeanMethods = false)
public class ConnectionManagerConfig {

    @Configuration(proxyBeanMethods = true)
    @ConditionalOnProperty(value = "cm.server.cluster.session", havingValue = "true")
    public static class SessionClusterConfig {

        private static final Logger log = LoggerFactory.getLogger(ConnectionManagerConfig.SessionClusterConfig.class);

        @Bean
        RedisMessageListenerContainer container(
                @Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory,
                @Autowired IPubSub listener) {
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            Topic topic = new ChannelTopic(listener.getTopic());
            MessageListenerAdapter adapter = new MessageListenerAdapter(listener, "onMessage");
            adapter.afterPropertiesSet();
            container.addMessageListener(adapter, topic);
            log.info("Registered MessageListenerAdapter[{}]", listener.getClass().getSimpleName());
            return container;
        }

        @Bean
        public IPubSub sessionPubsub(ServerConfig config, RedisTemplate<String, String> redisTemplate,
                @Value("${cm.server.cluster.topic:session_pub_sub}") String topic) {
            return new RedisSessionPubSub(config.getId(), redisTemplate, topic);
        }

    }

    /**
     * 没啥特别就是根据当前需要，暴露netty本身的属性配置，开发者可依据自身需要自行添加配置项
     * 
     * @author Hongyu
     */
    @ConfigurationProperties(prefix = "cm.server")
    public static class ServerConfig {
        /**
         * 集群环境下，用于唯一标识当前这个服务器的ID.
         * <p>
         * 假设A,B二台服务器部署，代码虽然一致，但是，其ID不一致，在一些需要明确设备标识的情况下，会非常有用。比如：
         * <li>设备登陆集群时的唯一会话管控
         */
        private String id = UUID.randomUUID().toString().replace("-", "");

        private int port = 9090;
        private int backlog = 1024;
        private int connectionTimeoutMills = 50 * 1000;
        private Boolean keepAlive = Boolean.TRUE;
        private Boolean tcpNoDeplay = Boolean.TRUE;
        private Integer io = Runtime.getRuntime().availableProcessors();
        private Integer workers = Runtime.getRuntime().availableProcessors() * 4;

        public Integer getIo() {
            return io;
        }

        public void setIo(Integer io) {
            this.io = io;
        }

        public Integer getWorkers() {
            return workers;
        }

        public void setWorkers(Integer workers) {
            this.workers = workers;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public int getBacklog() {
            return backlog;
        }

        public void setBacklog(int backlog) {
            this.backlog = backlog;
        }

        public int getConnectionTimeoutMills() {
            return connectionTimeoutMills;
        }

        public void setConnectionTimeoutMills(int connectionTimeoutMills) {
            this.connectionTimeoutMills = connectionTimeoutMills;
        }

        public Boolean getKeepAlive() {
            return keepAlive;
        }

        public void setKeepAlive(Boolean keepAlive) {
            this.keepAlive = keepAlive;
        }

        public Boolean getTcpNoDeplay() {
            return tcpNoDeplay;
        }

        public void setTcpNoDeplay(Boolean tcpNoDeplay) {
            this.tcpNoDeplay = tcpNoDeplay;
        }

    }

    @Bean
    @ConditionalOnMissingBean(value = IConnectionRepository.class)
    public IConnectionRepository memoryConnectionStorage() {
        return new IConnectionRepository() {
            Map<String, CEntry<String, Integer>> holder = new ConcurrentHashMap<>();

            @Override
            public void add(String equiementId, CEntry<String, Integer> entry, String serverId, long expiredAt) {
                holder.put(equiementId, entry);
            }

            @Override
            public void remove(String equiementId) {
                holder.remove(equiementId);
            }

            @Override
            public Optional<CEntry<String, Integer>> findById(String equiementId) {
                return Optional.ofNullable(holder.get(equiementId));
            }

            @Override
            public Map<String, CEntry<String, Integer>> findAll() {
                return this.holder;

            }

            @Override
            public void touch(String equiementId, long expiredAt) {
                // Nothing to do here
            }

        };

    }

    @Bean
    @ConditionalOnMissingBean(value = IEquipmentService.class)
    public IEquipmentService memoryEquipmentService() {
        return new IEquipmentService() {
            Map<String, EquipmentDTO> holder = new HashMap<>();

            @Override
            public EquipmentDTO findByKey(String key) {
                return holder.get(key);
            }

            @Override
            public void remove(String key) {
                holder.remove(key);
            }

            @Override
            public EquipmentDTO create(EquipmentDTO value) {
                value.setStatus(EquipmentStatus.Registered);
                holder.put(value.getId(), value);
                return value;
            }

            @Override
            public boolean activateById(String id) {
                holder.get(id).setStatus(EquipmentStatus.Activated);
                return true;
            }

            @Override
            public boolean isActivated(String id) {
                return holder.get(id).getStatus() == EquipmentStatus.Activated;
            }

            @Override
            public boolean isRegistered(String id) {
                return holder.get(id).getStatus() == EquipmentStatus.Registered;
            }

        };
    }

    @Bean
    @ConditionalOnMissingBean(value = IBusinessProtocalService.class)
    public IBusinessProtocalService emptyService() {
        return new IBusinessProtocalService() {

            @Override
            public boolean isReadSupport(byte[] packet) {
                return false;
            }

            @Override
            public IMessage read(byte[] packet) {
                return null;
            }

            @Override
            public void afterRead(IMessage msg) {
                // just ignore
            }

            @Override
            public boolean isWriteSupport(IMessage msg) {
                return false;
            }

            @Override
            public void write(IMessage msg) {
                // just ignore
            }
        };
    }
}
